﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace Workbench.Shared
{
    public class TextSegmentationService
    {
        public static TSResult Process(string text)
        {
            return Process(text, new Dictionary<string, TSUniqueWord>());
        }

        public static TSResult Process(string text, Dictionary<string, TSUniqueWord> uniqueWords)
        {
            text = text.Replace("\r\n", "\n");

            var result = new TSResult();
            result.Paragraphs = TokenizeParagraphs(text);
            result.Sentences = TokenizeSentences(text, result.Paragraphs);
            result.Words = TokenizeWords(text, result.Sentences, uniqueWords, out result.NewUniqueWords);

            #region DifferentForms
            HashSet<TSUniqueWord> uwCnt = new HashSet<TSUniqueWord>();
            result.Words.ForEach(x => uwCnt.Add(x.UniqueWord));
            result.DifferentForms = uwCnt.Count;
            #endregion

            #region MaximumNumberOfSentencesInParagraph
            Dictionary<TSParagraph, long> groupByParagraph = new Dictionary<TSParagraph, long>();
            foreach (var sentence in result.Sentences)
            {
                if (!groupByParagraph.ContainsKey(sentence.Paragraph))
                {
                    groupByParagraph.Add(sentence.Paragraph, 0);
                }
                groupByParagraph[sentence.Paragraph]++;
            }
            if(groupByParagraph.Values.Count > 0)
            {
                result.MaximumNumberOfSentencesInParagraph = groupByParagraph.Values.Max();
            }
            #endregion

            #region MaximumNumberOfWordsInSentence
            Dictionary<TSSentence, long> groupBySentence = new Dictionary<TSSentence, long>();
            foreach (var x in result.Words)
            {
                if (!groupBySentence.ContainsKey(x.Sentence))
                {
                    groupBySentence.Add(x.Sentence, 0);
                }
                groupBySentence[x.Sentence]++;
            }
            if (groupBySentence.Values.Count > 0)
            {
                result.MaximumNumberOfWordsInSentence = groupBySentence.Values.Max();
            }
            #endregion

            #region Averages

            Dictionary<TSSentence, long> avgSentenceInWords = new Dictionary<TSSentence,long>();
            Dictionary<TSParagraph, long> avgParagraphInWords = new Dictionary<TSParagraph,long>();
            Dictionary<TSParagraph, long> avgParagraphInSentences = new Dictionary<TSParagraph,long>();

            foreach (var word in result.Words)
            { 
                var sentence = word.Sentence;
                if(!avgSentenceInWords.ContainsKey(sentence))
                {
                    avgSentenceInWords.Add(word.Sentence, 0);
                }
                avgSentenceInWords[word.Sentence]++;

                var paragraph = word.Sentence.Paragraph;
                if (!avgParagraphInWords.ContainsKey(paragraph))
                {
                    avgParagraphInWords.Add(paragraph, 0);
                }
                avgParagraphInWords[paragraph]++;
            }
            if (avgSentenceInWords.Count > 0)
            {
                result.AverageLengthOfSentenceInWords = avgSentenceInWords.Average(x => x.Value);
            }
            if (avgParagraphInWords.Count > 0)
            {
                result.AverageLengthOfParagraphInWords = avgParagraphInWords.Average(x => x.Value);
            }
            foreach (var sentence in result.Sentences)
            {
                var paragraph = sentence.Paragraph;
                if (!avgParagraphInSentences.ContainsKey(paragraph))
                {
                    avgParagraphInSentences.Add(paragraph, 0);
                }
                avgParagraphInSentences[paragraph]++;
            }
            if(avgParagraphInSentences.Count > 0)
            {
                result.AverageLengthOfParagraphInSentences = avgParagraphInSentences.Average(x => x.Value);
            }
            #endregion

            return result;
        }

        private static int NewLineLength = 1;
        private static char NewLine = '\n';
        public static string ParagraphSeparator = string.Format("{0}{0}", NewLine);

        private static List<TSParagraph> TokenizeParagraphs(string text)
        {
            var result = new List<TSParagraph>();
            int startIndex = 0, endIndex, lastIndex = text.Length - 1;
            while (true)
            {
                if (startIndex > lastIndex)
                {
                    break;
                }
                endIndex = text.IndexOf(ParagraphSeparator, startIndex);
                if (endIndex == -1)
                {
                    endIndex = lastIndex;
                }

                int actualStart = startIndex, actualEnd = endIndex;
                while (actualStart <= lastIndex && char.IsWhiteSpace(text[actualStart])) actualStart++;
                while (actualEnd >= actualStart && char.IsWhiteSpace(text[actualEnd])) actualEnd--;

                if (actualStart <= actualEnd)
                {
                    var paragraph = new TSParagraph()
                    {
                        StartPosition = actualStart,
                        Length = actualEnd - actualStart + 1
                    };
                    result.Add(paragraph);
                }
                if (endIndex == lastIndex)
                    break;

                startIndex = endIndex + ParagraphSeparator.Length;
            }
            return result;
        }

        private static List<TSSentence> TokenizeSentences(string text, IEnumerable<TSParagraph> paragraphs)
        {
            var result = new List<TSSentence>();

            foreach (var paragraph in paragraphs)
            {
                var sentencesInParagraph = new List<TSSentence>();
                var paragraphContent = paragraph.GetContent(text);
                if (string.IsNullOrWhiteSpace(paragraphContent))
                    continue;

                int startIndex = 0;
                int oneAfterLastIndex = paragraph.Length;

                for (int endIndex = 0; endIndex < oneAfterLastIndex; endIndex++)
                {
                    char curChar = paragraphContent[endIndex];

                    if (curChar == '.' || curChar == '?' || curChar == '!' || curChar == '…')
                    {
                        if (endIndex + 1 >= oneAfterLastIndex
                        || paragraphContent[endIndex + 1] == ' '
                        || paragraphContent[endIndex + 1] == NewLine)
                        {
                            var actualStartIndex = startIndex;
                            char startChar = paragraphContent[actualStartIndex];
                            while (actualStartIndex <= endIndex && (startChar == '\t' || char.IsWhiteSpace(startChar)))
                            {
                                actualStartIndex++;
                                startChar = paragraphContent[actualStartIndex];
                            }

                            sentencesInParagraph.Add(new TSSentence()
                            {
                                StartPosition = actualStartIndex,
                                Length = endIndex - actualStartIndex + 1,
                                Paragraph = paragraph,
                            });
                            endIndex++;
                            startIndex = endIndex + 1;
                        }
                    }
                }
                if (sentencesInParagraph.Count == 0)
                {
                    sentencesInParagraph.Add(new TSSentence()
                    {
                        StartPosition = 0,
                        Length = paragraph.Length,
                        Paragraph = paragraph,
                    });
                }
                else
                {
                    if (sentencesInParagraph.Last().StartPosition != startIndex
                    && paragraphContent.Length - startIndex + 1 > 0)
                    {
                        sentencesInParagraph.Add(new TSSentence()
                        {
                            StartPosition = startIndex,
                            Length = paragraphContent.Length - startIndex + 1,
                            Paragraph = paragraph,
                        });
                    }
                }
                result.AddRange(sentencesInParagraph);
            }
            return result;
        }

        private static List<TSWord> TokenizeWords(string text, IEnumerable<TSSentence> sentences, Dictionary<string, TSUniqueWord> uniqueWords, out List<TSUniqueWord> newUniqueWords)
        {
            newUniqueWords = new List<TSUniqueWord>();
            var result = new List<TSWord>();
            TSUniqueWord uniqueWord = null;

            foreach (var sentence in sentences)
            {
                var sentenceContent = sentence.GetContent(text);

#if DEBUG
                Debug.Assert(sentenceContent.IndexOf("\r\n") < 0);
#endif
                var rows = sentenceContent.Split(new char[] { NewLine }, StringSplitOptions.RemoveEmptyEntries);
                int rowOffset = 0;
                foreach (var row in rows)
                {
                    int firstIndex = 0, lastIndex = row.Length - 1;
                    while (firstIndex < row.Length && char.IsWhiteSpace(row[firstIndex])) firstIndex++;
                    int startIndex = firstIndex, endIndex = firstIndex;

                    while (true)
                    {
                        if (startIndex >= row.Length)
                        {
                            break;
                        }
                        endIndex = row.IndexOf(' ', startIndex);
                        if (endIndex == -1) endIndex = lastIndex; //till the end
                        else endIndex--; // eat the space

                        int actualWordStart = startIndex, actualWordEnd = endIndex;
                        while (actualWordStart <= lastIndex && char.IsPunctuation(row[actualWordStart])) actualWordStart++;
                        while (actualWordEnd >= actualWordStart && char.IsPunctuation(row[actualWordEnd])) actualWordEnd--;

                        if (actualWordStart <= actualWordEnd)
                        {
                            int startPos = actualWordStart + rowOffset;
                            int len = actualWordEnd - actualWordStart + 1;
                            var wordContent = sentenceContent.Substring(startPos, len);

                            if (!StartsWithNumber(wordContent) && !IsDate(wordContent))
                            {
                                if (!uniqueWords.TryGetValue(wordContent, out uniqueWord))
                                {
                                    uniqueWord = new TSUniqueWord()
                                    {
                                        Content = wordContent
                                    };
                                    newUniqueWords.Add(uniqueWord);
                                    uniqueWords.Add(wordContent, uniqueWord);
                                }

                                result.Add(new TSWord()
                                {
                                    Sentence = sentence,
                                    UniqueWord = uniqueWord,
                                });
                            }
                        }
                        if (endIndex == lastIndex)
                            break;
                        endIndex++; //skip the space
                        startIndex = endIndex + 1;
                    }
                    rowOffset += row.Length + NewLineLength; //
                }
            }
            return result;
        }
         
        private static bool IsDate(string wordContent)
        {
            DateTime result;
            return DateTime.TryParse(wordContent, out result);
        }

        private static bool StartsWithNumber(string wordContent)
        {
            return wordContent[0] >= '0' && wordContent[0] <= '9';
        }
    }

    public class TSResult
    {
        public List<TSWord> Words;
        public List<TSSentence> Sentences;
        public List<TSParagraph> Paragraphs;
        public List<TSUniqueWord> NewUniqueWords;

        public long DifferentForms;
        public long MaximumNumberOfSentencesInParagraph;
        public long MaximumNumberOfWordsInSentence;

        public double AverageLengthOfSentenceInWords;
        public double AverageLengthOfParagraphInSentences;
        public double AverageLengthOfParagraphInWords;
    }

    public class TSParagraph
    {
        public int StartPosition;
        public int Length;

        public string GetContent(string text)
        {
            return text.Substring(StartPosition, Length);
        }
        public string GetContent(string text, int offset, int length)
        {
            return text.Substring(StartPosition + offset, length);
        }
    }

    public class TSSentence
    {
        public int StartPosition;
        public int Length;
        public TSParagraph Paragraph;

        public string GetContent(string text)
        {
            return Paragraph.GetContent(text, StartPosition, Length);
        }
        public string GetContent(string text, int offset, int length)
        {
            return Paragraph.GetContent(text, StartPosition + offset, length);
        }
    }

    public class TSWord
    {
        public int StartPosition;
        public int Length;
        public TSUniqueWord UniqueWord;
        public TSSentence Sentence;

        public string GetContent()
        {
            return this.UniqueWord.Content;
        }

    }

    public class TSUniqueWord
    {
        public string Content;
    }
}
